﻿using Protocol.Interface;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Net.Sockets;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;

namespace Protocol.TCP
{
    /// <summary>
    /// 异步连接的TcpClient, 有连接状态改变事件
    /// </summary>
    public class AsyncClient : INotifyPropertyChanged
    {
        protected TcpClient client;
        private bool connected;
        protected NetworkStream ns;

        protected int timeout = 1000;
        protected readonly int buffer_size = 1024 * 1024 * 8;
        private int fail_count = 0;

        public bool Connected
        {
            get
            {
                if (connected != ClientIsConnect())
                {
                    connected = ClientIsConnect();
                    NotifyPropertyChanged();
                }
                return connected;
            }
            set => connected = value;
        }

        public int Auto_disconnect { get; set; }

        public AsyncClient(int timeout = 1000, int auto_disconnect = 5)
        {
            this.timeout = timeout;
            Auto_disconnect = auto_disconnect;
            Connected = false;

            client = new TcpClient
            {
                ReceiveTimeout = this.timeout,
                SendTimeout = this.timeout
            };

        }

        /// <summary>
        /// 异步连接
        /// </summary>
        /// <param name="address">格式 127.0.0.1:12450</param>
        /// <param name="times">重试次数</param>
        /// <returns></returns>
        public async Task<bool> ConnectAsync(string address, int times = 5)
        {
            if (client == null)
                client = new TcpClient();
            if (!Connected)
            {
                DisConnect();
                client = new TcpClient
                {
                    ReceiveTimeout = timeout,
                    SendTimeout = timeout
                };
                string[] addresses = address.Split(':');
                if (addresses.Length != 2)
                {
                    throw new FormatException("地址格式不正确");
                }
                else
                {
                    bool connected = await TcpAsyncConnect.ConnectAsync(
                        client,
                        addresses[0],
                        Convert.ToInt32(addresses[1]),
                        times);
                    if (Connected)
                    {
                        ns = client.GetStream();
                        ns.ReadTimeout = timeout;
                        ns.WriteTimeout = timeout;
                    }
                    NotifyPropertyChanged("IsConnect");
                    return connected;
                }

            }
            NotifyPropertyChanged("IsConnect");
            return false;


        }
        /// <summary>
        /// 异步连接
        /// </summary>
        /// <param name="ip"></param>
        /// <param name="port"></param>
        /// <param name="times">重试次数</param>
        /// <returns></returns>
        public async Task<bool> ConnectAsync(string ip, int port, int times = 5)
        {
            if (client == null)
                client = new TcpClient();
            if (!Connected)
            {
                DisConnect();
                client = new TcpClient
                {
                    ReceiveTimeout = timeout,
                    SendTimeout = timeout
                };
                bool connected = await TcpAsyncConnect.ConnectAsync(client, ip, port, times);

                if (Connected)
                {
                    ns = client.GetStream();
                    ns.ReadTimeout = timeout;
                    ns.WriteTimeout = timeout;
                }
                NotifyPropertyChanged("IsConnect");
                return connected;
            }
            NotifyPropertyChanged("IsConnect");
            return false;
        }

        /// <summary>
        /// 发送数据
        /// </summary>
        /// <param name="msg"></param>
        /// <param name="encoding"></param>
        /// <returns></returns>
        public bool Send(string msg)
        {
            return Send(msg, Encoding.ASCII);
        }
        /// <summary>
        /// 发送数据
        /// </summary>
        /// <param name="msg">字符串</param>
        /// <param name="encoding">编码,默认ASCII</param>
        /// <returns>发送结果</returns>
        public bool Send(string msg, Encoding encoding)
        {
            byte[] buffer = encoding.GetBytes(msg);
            return Send(buffer);
        }
        /// <summary>
        /// 发送数据
        /// </summary>
        /// <param name="buffer">字节集</param>
        /// <returns>发送结果</returns>
        public bool Send(byte[] buffer)
        {
            try
            {
                if (ns != null && ns.CanWrite)
                {
                    //byte[] buffer = String2bytes(msg);// + "\r\n");
                    lock (client)
                    {
                        ns.Write(buffer, 0, buffer.Length);
                    }
                }
                else
                    return false;
            }
            catch (ObjectDisposedException e)
            {
                Console.WriteLine(e.ToString());
                return false;
            }
            catch (IOException e)
            {
                DisConnect();
                Console.WriteLine(e.ToString());
                return false;
            }
            return true;
        }

        /// <summary>
        /// 读取数据
        /// </summary>
        /// <param name="result"></param>
        /// <returns></returns>
        public bool Read(ref string result)
        {
            return Read(ref result, Encoding.ASCII);
        }
        /// <summary>
        /// 读取数据
        /// </summary>
        /// <param name="result">数据</param>
        /// <param name="encoding">编码,默认ASCII</param>
        /// <returns></returns>
        public bool Read(ref string result, Encoding encoding = null)
        {
            if (encoding == null)
                encoding = Encoding.ASCII;
            byte[] byte_receive = new byte[buffer_size];
            var len = Read(ref byte_receive);
            if (len > 0)
            {
                result = encoding.GetString(byte_receive, 0, len);
                return true;
            }
            else
            {
                return false;
            }
        }
        /// <summary>
        /// 读取数据
        /// </summary>
        /// <param name="byte_receive">接收的字节集</param>
        /// <returns>接收的字节集长度</returns>
        public int Read(ref byte[] byte_receive)
        {
            lock (client)
            {
                try
                {
                    if (ns != null && Connected)
                    {
                        if (ns.CanRead)
                        {
                            int len = ns.Read(byte_receive, 0, buffer_size);
                            if (len > 0)
                            {
                                //result = ByteArrayToStr(byteReceive, len);
                                //result = Encoding.ASCII.GetString(byte_receive, 0, len);
                                fail_count = 0;
                                return len;
                            }
                            else
                            {
                                AutoDisConnect();
                                return len;
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                    AutoDisConnect();
                    Console.WriteLine(ex.ToString());
                    return -1;
                }

            }
            return -1;
        }

        private bool ClientIsConnect()
        {
            if (client == null || client.Client == null)
                return false;
            return client.Client.Connected && client.Connected;
        }

        public void DisConnect()
        {
            if (ns != null)
                ns.Close();
            if (client.Client != null)
                client.Client.Close();
            client.Close();
            NotifyPropertyChanged("IsConnect");
        }

        /// <summary>
        /// 超时自动断连
        /// </summary>
        private void AutoDisConnect()
        {
            fail_count++;
            if (fail_count >= Auto_disconnect)
            {
                fail_count = 0;
                DisConnect();
            }
        }
        public event PropertyChangedEventHandler PropertyChanged;
        // This method is called by the Set accessor of each property.
        // The CallerMemberName attribute that is applied to the optional propertyName
        // parameter causes the property name of the caller to be substituted as an argument.
        private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}
